package nancal.mp.mvc.ops


import nancal.mp.db.mongo.JenkinsJobStatusEnum
import nancal.mp.db.mongo.JenkinsLanguageEnum
import nancal.mp.db.mongo.OpsWorkStatusEnum
import nancal.mp.db.mongo.entity.dev.*
import nancal.mp.db.mongo.extend.getAppDeploySetting
import nancal.mp.db.mongo.mor
import nancal.mp.extend.toBjTime
import nancal.mp.model.OpsDeployModel
import nancal.mp.service.getDockerfileContent
import nancal.mp.service.getK8sDeploymentYamlContent
import nancal.mp.service.git.GitApiService
import nancal.mp.service.jenkins.JenkinsPublishJob
import nancal.mp.service.jenkins.JenkinsService
import nancal.mp.service.jenkins.publish.getK8sFiles
import nbcp.base.annotation.Require
import nbcp.base.annotation.log.MyLogLevel
import nbcp.base.annotation.mvc.AdminSysOpsAction
import nbcp.base.annotation.mvc.OpenAction
import nbcp.base.comm.ApiResult
import nbcp.base.comm.JsonResult
import nbcp.base.comm.ListResult
import nbcp.base.db.CodeName
import nbcp.base.db.IdName
import nbcp.base.db.LoginUserModel
import nbcp.base.enums.LogLevelScopeEnum
import nbcp.base.extend.*
import nbcp.base.utils.CodeUtil
import nbcp.base.utils.FileUtil
import nbcp.base.utils.ZipUtil
import nbcp.mvc.HttpContext
import nbcp.mvc.sys.LoginUser
import nbcp.mvc.sys.setDownloadFileName
import nbcp.myoql.db.mongo.*
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*
import java.io.File
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

/**
 * 构建主任务
 */
@RequestMapping("/ops/jenkins")
@RestController
@AdminSysOpsAction
class OpsJenkinsController {
    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java.declaringClass)
    }

    @Autowired
    lateinit var jenkinsService: JenkinsService;

    /**
     * 获取默认的 Dockerfile
     */
    @RequestMapping("/default-dockerfile", method = arrayOf(RequestMethod.GET, RequestMethod.POST))
    fun defaultDockerfile(
        @Require language: JenkinsLanguageEnum,
        @Require languageVersion: String,
        targetEnv: String
    ): ApiResult<String> {
        var text = language.getDockerfileContent(targetEnv)
        if (text.isNullOrEmpty()) {
            throw RuntimeException("找不到 dockerfile 资源");
        }
        return ApiResult.of(text)
    }

    @RequestMapping(
        "/default-deployment-yaml",
        method = arrayOf(RequestMethod.GET, RequestMethod.POST)
    )
    fun defaultDeploymentYaml(
        @Require language: JenkinsLanguageEnum,
        @Require languageVersion: String,
        targetEnv: String
    ): ApiResult<String> {
        var deploymentYaml = language.getK8sDeploymentYamlContent(languageVersion, targetEnv)
        if (deploymentYaml.isNullOrEmpty()) {
            throw RuntimeException("找不到 k8s-deployment 资源");
        }
        return ApiResult.of(deploymentYaml)
    }

    /**
     * 构建发布单
     */
    @PostMapping("/deploy-sheet/{id}")
    fun deploySheet(
        id: String,
        loginUser: LoginUserModel
    ): ListResult<CodeName> {
        var sheet = mor.dev.deploySheet.queryById(id).toEntity()
            .throwIfNull { "找不到数据" }

        var list = sheet.items.map { deploy ->
            var m = OpsDeployModel();
            m.gitId = deploy.gitData!!.id;
            m.name = deploy.buildFolder
            m.gitBranch = deploy.gitBranch;
            m.clusterCode = deploy.gitData!!.clusterCode;

            var jobResult = genAndBuildJob(m, loginUser)

            var log = mor.dev.jenkinsLog.queryById(jobResult.data.AsString()).toEntity()

            //定时任务同步数据
            deploy.deployLog = log;
            return@map jobResult
        }.map { CodeName(it.data.AsString(), it.msg.AsString()) }

        mor.dev.deploySheet.updateById(id)
            .set { it.items to sheet.items }
            .exec()

        return ListResult.of(list);
    }


    class LockStatusVO(
        var canDeploy: Boolean = false,
        var canUnlock: Boolean = false,
        var lockInfo: String = ""
    )

    @PostMapping("/lock-status")
    fun canDeploy(
        @RequestBody query: OpsDeployModel,
        loginUser: LoginUserModel
    ): ApiResult<LockStatusVO> {
        var lockInfo = mor.dev.gitFolderDeployLock.query()
            .where { it.gitDataId mongoEquals query.gitId }
            .where { it.folder mongoEquals query.name }
            .toEntity();

        if (lockInfo == null) {
            return ApiResult.of(LockStatusVO(true, false, ""));
        }

        if (lockInfo.lockBranch.isNullOrEmpty() || lockInfo.lockBy.id.isNullOrEmpty()) {
            return ApiResult.of(LockStatusVO(true, false, ""));
        }

        //如果部署的分支与锁定分支相同， 则可以部署。
        if (lockInfo.lockBranch == query.gitBranch) {
            return ApiResult.of(LockStatusVO(true, false, "${lockInfo.lockBy.name} 锁定了 ${lockInfo.lockBranch}"));
        }

        //如果锁定者和发布者相同，则可以解锁。
        if (lockInfo.lockBy.id == loginUser.id) {
            return ApiResult.of(LockStatusVO(false, true, "${lockInfo.lockBy.name} 锁定了 ${lockInfo.lockBranch}"));
        }

        //如果是管理员，可以先解锁
        if (loginUser.isAdmin) {
            return ApiResult.of(LockStatusVO(false, true, "${lockInfo.lockBy.name} 锁定了 ${lockInfo.lockBranch}"));
        }

        //最后，分支不同，锁定者也不同。
        return ApiResult.of(LockStatusVO(false, false, "${lockInfo.lockBy.name} 锁定了 ${lockInfo.lockBranch}"));
    }

    @PostMapping("/unlock-deploy")
    fun unlockDeploy(
        @RequestBody query: OpsDeployModel,
        loginUser: LoginUserModel
    ): JsonResult {
        var lockInfo = mor.dev.gitFolderDeployLock.query()
            .where { it.gitDataId mongoEquals query.gitId }
            .where { it.folder mongoEquals query.name }
            .toEntity();

        if (lockInfo == null) {
            return JsonResult();
        }

        if (!loginUser.isAdmin && lockInfo.lockBy.id != loginUser.id) {
            return JsonResult.error("必须由 ${lockInfo.lockBy.name} 解锁 ${lockInfo.lockBranch}")
        }

        mor.dev.gitFolderDeployLock.deleteById(lockInfo.id).exec();
        return JsonResult();
    }


    @PostMapping("/lock-deploy")
    fun lockDeploy(
        @RequestBody query: OpsDeployModel,
        loginUser: LoginUserModel
    ): JsonResult {
        var lockInfo = mor.dev.gitFolderDeployLock.query()
            .where { it.gitDataId mongoEquals query.gitId }
            .where { it.folder mongoEquals query.name }
            .toEntity();

        if (lockInfo != null) {
            if (!loginUser.isAdmin && lockInfo.lockBy.id != loginUser.id) {
                if (lockInfo.lockBranch.HasValue && lockInfo.lockBy.id.HasValue) {
                    return JsonResult.error("已被 ${lockInfo.lockBy.name} 锁定 ${lockInfo.lockBranch}")
                }
            }


            mor.dev.gitFolderDeployLock.deleteById(lockInfo.id).exec();
        }

        lockInfo = GitFolderDeployLock();
        lockInfo.gitDataId = query.gitId;
        lockInfo.folder = query.name
        lockInfo.lockBranch = query.gitBranch
        lockInfo.lockBy = IdName(loginUser.id, loginUser.name)

        mor.dev.gitFolderDeployLock.doInsert(lockInfo);

        return JsonResult();
    }

    /**
     * 🚩构建Jenkins任务
     */
    @PostMapping("/deploy-job")
    fun genAndBuildJob(
        @RequestBody query: OpsDeployModel,
        loginUser: LoginUserModel
    ): ApiResult<String> {
        if (query.gitId.isEmpty()) {
            throw RuntimeException("gitId参数不能为空")
        }

        var deploySetting = mor.dev.appDeploySetting.getAppDeploySetting(query.targetEnv);
        if (deploySetting.jenkinsServer.jenkinsHttpUrl.isEmpty()) {
            logger.Important("使用消息队列，异步构建:" + deploySetting.jenkinsServer.jenkinsHttpUrl);
            var dbOne = mor.dev.opsBuildQueue.query()
                .where { it.gitId mongoEquals query.gitId }
                .where { it.targetEnv mongoEquals query.targetEnv }
                .where { it.clusterCode mongoEquals query.clusterCode }
                .where { it.name mongoEquals query.name }
                .where { it.gitBranch mongoEquals query.gitBranch }
                .where { it.status mongoIn listOf(OpsWorkStatusEnum.waiting, OpsWorkStatusEnum.working) }
                .toEntity();


            if (dbOne != null) {
                mor.dev.opsBuildQueue.updateById(dbOne.id)
                    .set { it.status to OpsWorkStatusEnum.cancel }
                    .exec();
            }

            //仅发送一个消息。
            if (mor.dev.opsBuildQueue.query()
                    .where { it.status mongoEquals OpsWorkStatusEnum.waiting }
                    .count() > 20
            ) {
                return ApiResult.error("队列任务已满，稍后再试")
            }


            var ent = OpsBuildQueue();
            ent.name = query.name
            ent.gitBranch = query.gitBranch
            ent.gitId = query.gitId
            ent.clusterCode = query.clusterCode
            ent.createBy = IdName(loginUser.id, loginUser.name);
            ent.targetEnv = query.targetEnv
            ent.status = OpsWorkStatusEnum.waiting

            mor.dev.opsBuildQueue.doInsert(ent)
            return ApiResult();
        } else {
            logger.Important("使用Jenkins构建:" + deploySetting.jenkinsServer.jenkinsHttpUrl);
        }

        var jenkinsLog = createJenkinsLog(query);


        var buildResult = jenkinsService.buildPublishJob(
            deploySetting.jenkinsServer.jenkinsHttpUrl,
            deploySetting.jenkinsServer.userName,
            deploySetting.jenkinsServer.password,
            jenkinsLog.id
        )

        if (buildResult.msg.HasValue) {
            return ApiResult.error(buildResult.msg)
        }

        return ApiResult.of(jenkinsLog.id);
    }


    /**
     * 仅有 buildInfo.jenkinsNumber 字段不能墳充, 其它全部填充
     */
    fun createJenkinsLog(query: OpsDeployModel): JenkinsLog {
        var gitData = mor.dev.gitData.queryById(query.gitId).toEntity();
        if (gitData == null) {
            throw RuntimeException("找不到 git 数据")
        }

        val productLineInfo = mor.dev.productLine.queryByCode(gitData.productLine.code).toEntity()
        if (productLineInfo == null) {
            throw RuntimeException("找不到 ${gitData.productLine.code} 产品线")
        }


        var appFolder = gitData.folders.firstOrNull { it.name == query.name };
        if (appFolder == null) {
            throw RuntimeException("GitData中找不到 ${query.name} 应用")
        }

        var setting = mor.dev.appDeploySetting.getAppDeploySetting(query.targetEnv);

        var gitBuildInfo = JenkinsBuildInfo();
        gitBuildInfo.buildFolder = appFolder.path
        gitBuildInfo.appName = appFolder.name
        gitBuildInfo.location = appFolder.location
        gitBuildInfo.gitBranch = query.gitBranch
        gitBuildInfo.appCnName = appFolder.cnname
        gitBuildInfo.serviceName =
            setting.serviceNameStyle.getServiceName(gitBuildInfo.appName, gitData.productLine.code, gitData.language);
        gitBuildInfo.msDeployEnvEnum = query.targetEnvDeploySetting.clusterDeployEnv

        gitBuildInfo.imageTagPrefix = query.targetEnvDeploySetting.harborServer.getHarborImagePrefix(gitData.language)

        gitBuildInfo.targetEnv = query.targetEnv;
        gitBuildInfo.profile = query.targetEnvDeploySetting.profile;
        gitBuildInfo.clusterCode = query.clusterCode
        gitBuildInfo.isLib = gitData.isLib

        //日志
        var jenkinsLog = JenkinsLog();
        jenkinsLog.status = JenkinsJobStatusEnum.None
        jenkinsLog.createBy = HttpContext.request.LoginUser.asIdName();
        jenkinsLog.gitData = gitData;
        jenkinsLog.buildInfo = gitBuildInfo;
        mor.dev.jenkinsLog.doInsert(jenkinsLog);
        return jenkinsLog
    }


    @PostMapping("/job/log")
    fun getLog(
        targetEnv: String,
        @Require jobName: String
    ): ApiResult<String> {
        var deploySetting = mor.dev.appDeploySetting.getAppDeploySetting(targetEnv);

        jenkinsService.getLog(
            deploySetting.jenkinsServer.jenkinsHttpUrl,
            deploySetting.jenkinsServer.userName,
            deploySetting.jenkinsServer.password,
            jobName,
            0
        ).apply {
            var ret = ApiResult<String>();
            ret.msg = this.msg;
            ret.data = this.data?.content;
            ret.value = this.value;
            return ret;
        }
    }

    @PostMapping("/job/delete")
    fun delete(
        targetEnv: String,
        @Require jobName: String
    ): JsonResult {
        val deploySetting = mor.dev.appDeploySetting.getAppDeploySetting(targetEnv);

        jenkinsService.delete(
            deploySetting.jenkinsServer.jenkinsHttpUrl,
            deploySetting.jenkinsServer.userName,
            deploySetting.jenkinsServer.password,
            jobName
        ).also {
            return JsonResult()
        }
    }


//    @Async
//    @GetMapping("/save-job/{jobId}")
//    fun saveJob(jobId: String) {
//        var log = mor.dev.jenkinsLog.queryById(jobId).toEntity();
//        if( log == null || log.gitData == null){
//            return;
//        }
//
//
//        var deploySetting = mor.dev.appDeploySetting.getAppDeploySetting(log.buildInfo.targetEnv);
//        var jenkinsJob = jenkinsService.getJenkinsClient(
//            deploySetting.jenkinsServer.jenkinsHttpUrl,
//            deploySetting.jenkinsServer.userName,
//            deploySetting.jenkinsServer.password
//        )
//            .getJob(log.buildInfo.jobName)
//
//        if (jenkinsJob == null) {
//            return ;
//        }
//        var buildNumber = jenkinsJob.nextBuildNumber - 1
//
//        mor.dev.jenkinsLog.update()
//            .where { it.buildInfo.jobName mongoEquals log.buildInfo.jobName }
//            .where { it.buildInfo.buildNumber lessThan buildNumber }
//            .where { it.status mongoEquals JenkinsJobStatusEnum.Working }
//            .set { it.status to JenkinsJobStatusEnum.Expired }
//            .exec()
//
//
//        var version = JenkinsBuildDetailInfo();
//        mor.dev.jenkinsLog.updateById(jobId)
//            .push { it.buildVersions to version }
//            .set { it.buildInfo.buildNumber to buildNumber }
//            .set { it.status to JenkinsJobStatusEnum.Prepare }
//            .exec()
//    }

    fun getBjTimeImageVersion(): String {
        return LocalDateTime.now().toBjTime().Format(versionStyle)
    }

    fun LocalDateTime?.getBjTime(): String {
        if (this == null) {
            return "";
        }
        return this.toBjTime()
            .format(DateTimeFormatter.ofPattern(versionStyle))
    }


    val versionStyle = "yyyyMMdd.HHmmss"

    @GetMapping("/start-job/{jobId}")
    fun startJob(jobId: String): String {
        var errorMsg = """
function start-job(){
    echo "构建信息不完整!"
    exit 1
}
"""

        var log =
            mor.dev.jenkinsLog.queryById(jobId).toEntity().must().elseReturn { return errorMsg }

        var version = JenkinsBuildDetailInfo();
        var gitData = log.gitData
        if (gitData != null) {
            var gitServer = mor.dev.gitServer.queryById(gitData.server.id).toEntity().must()
                .elseReturn { return errorMsg }

            var sects = gitData.url.split("/");
            var git_owner = sects[sects.size - 2];
            var git_repo = sects.last().Slice(0, -4);

            var git = GitApiService.getInstance(gitServer.gitServerType)
                .getBranchCommitInfo(
                    gitServer.host,
                    gitData.accessToken.AsString(gitServer.accessToken),
                    git_owner,
                    git_repo,
                    log.buildInfo.gitBranch
                );

            if (git.msg.isNullOrEmpty() && git.data != null) {
                version.dockerImageVersion = getBjTimeImageVersion()
                version.gitCommitId = git.data!!.commitId
                version.gitCommitAt = git.data!!.commitAt.getBjTime()
            }

        } else {
            var source = log.sourceData
            if (source != null) {
                version.dockerImageVersion = getBjTimeImageVersion()
            }
        }

        if (version.dockerImageVersion.isEmpty()) {
            return errorMsg
        }


        var deploySetting = mor.dev.appDeploySetting.getAppDeploySetting(log.buildInfo.targetEnv);
        var jenkinsJob = jenkinsService.getJenkinsClient(
            deploySetting.jenkinsServer.jenkinsHttpUrl,
            deploySetting.jenkinsServer.userName,
            deploySetting.jenkinsServer.password
        )
            .getJob(log.buildInfo.serviceName)

        var buildNumber = 1;
        if (jenkinsJob != null) {
            buildNumber = jenkinsJob.nextBuildNumber - 1
        }

        var jenkinsAt = Date(jenkinsJob.firstBuild.details().timestamp).AsLocalDateTime()!!

        mor.dev.jenkinsLog.update()
            .where { it.buildInfo.serviceName mongoEquals log.buildInfo.serviceName }
            .where { it.createAt lessThan log.createAt }
            .where { it.status mongoEquals JenkinsJobStatusEnum.Working }
            .set { it.status to JenkinsJobStatusEnum.Expired }
            .exec()

        mor.dev.jenkinsLog.updateById(jobId)
            .set { it.buildVersion to version }
            .set { it.status to JenkinsJobStatusEnum.Prepare }
            .exec()

        return """
function start-job(){
    docker_image_version="${version.dockerImageVersion}"
    git_commit_id="${version.gitCommitId}"
    git_commit_time="${version.gitCommitAt}"
}
"""
    }

//    @GetMapping("/feign-jar/{jobId}")
//    fun get_feign_jar(
//        @Require jobId: String,
//        response: HttpServletResponse
//    ) {
//        var jenkinsLog = mor.dev.jenkinsLog.queryById(jobId).toEntity()
//            .must()
//            .elseThrow { "找不到JenkinsLog" }
//
//        var appSetting =
//            mor.dev.appDeploySetting.getAppDeploySetting(jenkinsLog.buildInfo.targetEnv);
//
//
//
//        if (currentAppNancalFeignUrl.isEmpty()) {
//            return
//        }
//
//        var http = HttpInvoke()(currentAppNancalFeignUrl);
//        http.request.httpMethod = HttpMethod.GET
//        var bytes = byteArrayOf()
//        http.resultAction = { input ->
//            bytes = input.readBytes()
//        }
//        http.doNet()
//
//        if (bytes.size == 0) {
//
//            response.WriteHtmlValue("下载 nancal-feign 失败")
//            response.status = 500
//        } else {
//            response.outputStream.write(bytes)
//            response.setDownloadFileName("${appSetting.nancalFeignJar.artifactId}-${appSetting.nancalFeignJar.version}.jar") //nancal-feign-1.0.6
//        }
//    }

    /**
     * 获取构建的脚本
     */
    @GetMapping("/cmd/file/{jobId}")
    fun get_cmd_file(
        @Require jobId: String,
        debug: Boolean?,
        response: HttpServletResponse
    ) {
        var log =
            mor.dev.jenkinsLog.queryById(jobId).toEntity().must().elseThrow { "找不到构建任务信息" }

        var file = File(
            FileUtil.resolvePath(
                System.getProperty("java.io.tmpdir"),
                "jenkins-ops-cmd-${CodeUtil.getCode()}.zip"
            )
        )


        var version = JenkinsBuildDetailInfo();
        if (log.gitData != null) {
            var gitData = log.gitData!!;
            var gitServer = mor.dev.gitServer.queryById(gitData.server.id).toEntity()
                .throwIfNull { "找不到gitServer" }

            var sects = gitData.url.split("/");
            var git_owner = sects[sects.size - 2];
            var git_repo = sects.last().Slice(0, -4);

            var git = GitApiService.getInstance(gitServer.gitServerType)
                .getBranchCommitInfo(
                    gitServer.host,
                    gitData.accessToken.AsString(gitServer.accessToken),
                    git_owner,
                    git_repo,
                    log.buildInfo.gitBranch
                );

            if (git.msg.isNullOrEmpty() && git.data != null) {
                if (log.buildVersion.dockerImageVersion.isEmpty()) {
                    version.dockerImageVersion = getBjTimeImageVersion();
                }
                version.gitCommitId = git.data!!.commitId
                version.gitCommitAt = git.data!!.commitAt.getBjTime()
            }
        } else {
            var source = log.sourceData
            if (source != null) {
                if (log.buildVersion.dockerImageVersion.isEmpty()) {
                    version.dockerImageVersion = getBjTimeImageVersion()
                }
            }
        }


        var deploySetting = mor.dev.appDeploySetting.getAppDeploySetting(log.buildInfo.targetEnv);
        var jenkinsJob = jenkinsService.getJenkinsClient(
            deploySetting.jenkinsServer.jenkinsHttpUrl,
            deploySetting.jenkinsServer.userName,
            deploySetting.jenkinsServer.password
        )
            .getJob(log.buildInfo.serviceName)

        var buildNumber = 1;
        if (jenkinsJob != null) {
            buildNumber = jenkinsJob.nextBuildNumber - 1
        }

        mor.dev.jenkinsLog.updateById(jobId)
            .set { it.status to JenkinsJobStatusEnum.Prepare }
            .set { it.buildVersion to version }
            .set { it.buildInfo.buildNumber to buildNumber }
            .exec()

        log = mor.dev.jenkinsLog.queryById(jobId).toEntity()!!

        var zip = ZipUtil.beginCompress(file)
        try {
            var job = JenkinsPublishJob(log);
            job.getK8sFiles()
                .forEach {
                    if (it.bytes.any()) {
                        zip.addFile(it.bytes.inputStream(), it.fileName)
                    }
                }

            response.outputStream.write(file.readBytes())
            response.setDownloadFileName("jenkins-ops-cmd.zip")
        } finally {
            file.deleteOnExit()
        }
    }


    @MyLogLevel(LogLevelScopeEnum.ERROR)
    @PostMapping("/last-log")
    fun last_tasks(
        @Require gitId: String,
        @Require branch: String,
        @Require appName: String,
        lastStage: Boolean?,
        request: HttpServletRequest
    ): ListResult<JenkinsLogItemData> {
        mor.dev.jenkinsLog.query()
            .where { it.gitData.id mongoEquals gitId }
            .where {
                it.buildInfo.gitBranch mongoIn branch.split(",").map { it.trim() }
                    .filter { it.HasValue }
            }
            .where { it.buildInfo.appName mongoEquals appName }
            .apply {
                if (lastStage != null && lastStage) {
                    this.selectArrayLast({ it.stages }, 1)
                }
            }
            .orderByDesc { it.id }
            .toEntity()
            .apply {
                if (this == null) {
                    return ListResult.error("找不到Jenkins发版信息")
                }
                if (this.status == JenkinsJobStatusEnum.Success) {
                    return ListResult.of(this.stages).withValue(true)
                } else if (this.status == JenkinsJobStatusEnum.Failure) {
                    return ListResult.of(this.stages).withValue(true)
                }
                return ListResult.of(this.stages)
            }
    }
}
